﻿/*
 *  Modified from SlimDX sample code, original license below
 */
/*
* Copyright (c) 2007-2011 SlimDX Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using SlimDX;
using SlimDX.Direct3D10;
using SlimDX.DXGI;
using Resource = SlimDX.Direct3D10.Resource;
using DirectCanvas.Scenes;
using DirectCanvas;
using System.Diagnostics;
using DirectCanvas.Brushes;

namespace ThreeDSample
{
    internal class D3DScene : Scene
    {
        public enum SceneOption
        {
            TextOver3D,
            TextWith3DBrush,
            TextOn3D
        }
        public SceneOption RenderOption { get; set; }

        public D3DScene(Presenter presenter)
        {
            RenderOption = SceneOption.TextOver3D;
            m_presenter = presenter;

            m_layer = presenter.Factory.CreateDrawingLayer(m_presenter.Width, m_presenter.Height);
            m_layerText = presenter.Factory.CreateDrawingLayer(m_presenter.Width, m_presenter.Height);
        }

        private float GetSecondsSinceLastUpdate()
        {
            long newValue = Stopwatch.GetTimestamp();
            float ret = (float)(newValue - lastStopwatchValue) / Stopwatch.Frequency;
            lastStopwatchValue = newValue;
            return ret;
        }

        public override void ActivateScene()
        {
            m_texture = Texture2D.FromPointer(m_layer.Texture2DComPointer);
            m_device = m_texture.Device;
            renderTargetView = RenderTargetView.FromPointer(m_layer.RenderTargetViewComPointer);
            LoadResources();
        }

        private void LoadResources()
        {
            var imageLayer = m_presenter.Factory.CreateDrawingLayerFromFile(@".\Assets\Nature Mountains photo.jpg");
            m_brush = m_presenter.Factory.CreateDrawingLayerBrush(imageLayer);
            m_brushD3D = m_presenter.Factory.CreateDrawingLayerBrush(m_layer);
            lastStopwatchValue = Stopwatch.GetTimestamp();

            CreateDepthBuffer();

            var dssd = new DepthStencilStateDescription
            {
                IsDepthEnabled = true,
                IsStencilEnabled = false,
                DepthWriteMask = DepthWriteMask.All,
                DepthComparison = Comparison.Less
            };

            var solidParentOp = new BlendStateDescription();
            solidParentOp.SetBlendEnable(0, false);
            solidParentOp.SetWriteMask(0, ColorWriteMaskFlags.All);

            var transParentOp = new BlendStateDescription
            {
                AlphaBlendOperation = BlendOperation.Add,
                BlendOperation = BlendOperation.Add,
                DestinationAlphaBlend = BlendOption.Zero,
                DestinationBlend = BlendOption.One,
                IsAlphaToCoverageEnabled = false,
                SourceAlphaBlend = BlendOption.Zero,
                SourceBlend = BlendOption.One,
            };

            transParentOp.SetBlendEnable(0, true);
            transParentOp.SetWriteMask(0, ColorWriteMaskFlags.All);

            transBlendState = BlendState.FromDescription(m_device, transParentOp);
            solidBlendState = BlendState.FromDescription(m_device, solidParentOp);

            depthStencilState = DepthStencilState.FromDescription(m_device, dssd);
            
            textTex = Texture2D.FromPointer(m_layerText.Texture2DComPointer);
            jupiterTex = Texture2D.FromFile(m_device, "jupiter.jpg");

            jupiterMesh = new SimpleModel(m_device, "SimpleModel10.fx", "jupiter.SMD", jupiterTex);

            view = Matrix.LookAtLH(new Vector3(0, 160, 0), new Vector3(0, -128.0f, 0), -Vector3.UnitZ);
            jupiterMesh.Effect.GetVariableByName("view").AsMatrix().SetMatrix(view);

            proj = Matrix.PerspectiveFovLH(45.0f, m_presenter.Width / (float)m_presenter.Height, 1.0f, 1000.0f);
            jupiterMesh.Effect.GetVariableByName("proj").AsMatrix().SetMatrix(proj);
        }

        void EnsureTexture()
        {
            if (RenderOption == SceneOption.TextOn3D)
            {
                jupiterMesh.SetTexture(textTex);
            }
            else
            {
                jupiterMesh.SetTexture(jupiterTex);
            }
        }

        private void CreateDepthBuffer()
        {
            var depthBufferDesc = new Texture2DDescription
            {
                ArraySize = 1,
                BindFlags = BindFlags.DepthStencil,
                CpuAccessFlags = CpuAccessFlags.None,
                Format = Format.D32_Float,
                Height = m_presenter.Height,
                Width = m_presenter.Width,
                MipLevels = 1,
                OptionFlags = ResourceOptionFlags.None,
                SampleDescription = new SampleDescription(1, 0),
                Usage = ResourceUsage.Default
            };

            using (var depthBuffer = new Texture2D(m_device, depthBufferDesc))
            {
                depthStencilView = new DepthStencilView(m_device, depthBuffer);
            }
        }

        public override void DeactivateScene()
        {
            depthStencilView.Dispose();

            renderTargetView.Dispose();
            jupiterMesh.Dispose();
            depthStencilState.Dispose();
            solidBlendState.Dispose();
            transBlendState.Dispose();
            jupiterTex.Dispose();
            textTex.Dispose();
        }

        public override void Render()
        {
            m_device.OutputMerger.DepthStencilState = depthStencilState;
            m_device.OutputMerger.SetTargets(depthStencilView, renderTargetView);
            m_device.Rasterizer.SetViewports(new Viewport(0, 0, m_presenter.Width, m_presenter.Height, 0.0f, 1.0f));
            m_device.ClearRenderTargetView(renderTargetView, new SlimDX.Color4(0.0f, 0.0f, 0.0f));
            m_device.ClearDepthStencilView(depthStencilView, DepthStencilClearFlags.Depth, 1.0f, 0);
            m_presenter.Clear();

            EnsureTexture();

            if (RenderOption == SceneOption.TextOn3D)
            {
                m_layerText.Clear();
                m_layerText.BeginDraw();
                for (int i = 10; i < 550; i += 45)
                {
                    m_layerText.DrawText(m_brush, "Direct3D + DirectCanvas = <3", 100, i, 42);
                }
                m_layerText.EndDraw();
            }

            rotation += (float)(Math.PI / 4.0f * GetSecondsSinceLastUpdate());

            if (rotation > 2 * Math.PI)
                rotation = 0;

            /// First we'll setup the primary planet that we want to render. This is the big jupiter planet.
            /// We're not doing anything overly fancy here, just setting it up to rotate on its axis at a fixed rate.
            Matrix rotationMatrix;
            Matrix.RotationZ(rotation, out rotationMatrix);
            var world = Matrix.Identity;
            Matrix.Multiply(ref world, ref rotationMatrix, out world);

            jupiterMesh.Effect.GetVariableByName("transparency").AsScalar().Set(0.0f);
            jupiterMesh.Effect.GetVariableByName("world").AsMatrix().SetMatrix(world);
            m_device.OutputMerger.BlendState = solidBlendState;
            jupiterMesh.Draw();

            /// At this point we want to render a small moon that will orbit the planet (and rotate as well). 
            /// Again, we're not doing anything overly fancy, however we will set the transparency blend state and then
            /// set the transparency of the pixels (a fixed amount).
            Matrix.Translation(0, 72, 0, out world);
            Matrix tempMatrix;
            Matrix.Scaling(.125f, .125f, .125f, out tempMatrix);
            Matrix.Multiply(ref tempMatrix, ref world, out world);
            Matrix.Multiply(ref rotationMatrix, 1.25f, out rotationMatrix);
            Matrix.Multiply(ref rotationMatrix, ref world, out world);
            Matrix orbitMatrix;
            Matrix.RotationZ(-rotation * 2f, out orbitMatrix);
            Matrix.Multiply(ref world, ref orbitMatrix, out world);

            jupiterMesh.Effect.GetVariableByName("world").AsMatrix().SetMatrix(world);
            jupiterMesh.Effect.GetVariableByName("transparency").AsScalar().Set(.50f);
            m_device.OutputMerger.BlendState = transBlendState;
            jupiterMesh.Draw();

            if (RenderOption == SceneOption.TextOver3D)
            {
                //Overlay text using the mountain image brush
                m_layer.BeginDraw();
                for (int i = 10; i < 550; i += 45)
                {
                    m_layer.DrawText(m_brush, "Direct3D + DirectCanvas = <3", 100, i, 42);
                }
                m_layer.EndDraw();
            }
            else if (RenderOption == SceneOption.TextWith3DBrush)
            {
                //Draw text using Direct3D scene as a brush!
                m_presenter.BeginDraw();
                for (int i = 10; i < 550; i += 36)
                {
                    m_presenter.DrawText(m_brushD3D, "Direct3D + DirectCanvas = <3", 100, i, 42);
                }
                m_presenter.EndDraw();
            }

            if (RenderOption == SceneOption.TextOn3D ||
                RenderOption == SceneOption.TextOver3D)
            {
                m_presenter.BeginDraw();
                m_presenter.DrawLayer(m_layer);
                m_presenter.EndDraw();
            }
        }

        #region Implementation Detail

        private readonly Presenter m_presenter;

        Texture2D m_texture;
        SlimDX.Direct3D10.Device m_device;

        ///
        /// Disposable resources
        ///
        private Texture2D jupiterTex;
        private Texture2D textTex;
        private DepthStencilState depthStencilState;
        private DepthStencilView depthStencilView;
        private SimpleModel jupiterMesh;
        private BlendState solidBlendState;
        private BlendState transBlendState;
        private RenderTargetView renderTargetView;

        /// 
        /// Non-Disposable resources
        ///
        private Brush m_brush;
        private Brush m_brushD3D;
        private DrawingLayer m_layer;
        private DrawingLayer m_layerText;
        private Matrix proj;
        private float rotation;
        private Matrix view;
        private long lastStopwatchValue;

        #endregion

    }
}